package flexo.graphic;

import java.util.List;
import java.util.LinkedList;
import java.util.Collections;

import flexo.math.Vector;
import flexo.common.LogMsgType;
import flexo.graphic.entity.*;

public class GraphicSystem extends flexo.common.System
{
	public static final String ID = "graphic";
	
	private Window window;
	private List<Camera> cameraList;
	boolean sortCameraList = false;
	
	ShaderPool shaderPool;
	MaterialPool materialPool;
	GeometryPool geometryPool;
	TexturePool texturePool;
	FontPool fontPool;
	SpriteSheetPool spriteSheetPool;
	
	public GraphicSystem(String windowTitle)
	{
		this(640,480,windowTitle,false);
	}
	
	public GraphicSystem(int width, int height, String windowTitle, boolean fullScreen)
	{
		super(ID,flexo.common.System.UPDATE_STANDARD);
		flexo.Kernel.log("flexo.graphic.GraphicSystem", "Starting graphic module");
		window = new Window(width, height, windowTitle, fullScreen); 
		
		cameraList = new LinkedList<Camera>();
		
		geometryPool = new GeometryPool();
		shaderPool = new ShaderPool();
		materialPool = new MaterialPool();
		texturePool = new TexturePool();
		fontPool = new FontPool();
		spriteSheetPool = new SpriteSheetPool();
	}
	
	public float getAspectRatio()
	{
		return getDisplayMode().x / getDisplayMode().y;
	}
	
	/**
	 * Cleans unused resources.
	 */
	public void cleanResources()
	{
		flexo.Kernel.log("flexo.graphic.GraphicSystem", "Cleaning graphic resources");
		fontPool.clean();
		spriteSheetPool.clean();
		materialPool.clean();
		shaderPool.clean();
		geometryPool.clean();				
		texturePool.clean();		
	}
	
	public void addFrameListener(FrameListener f)
	{
		window.frameListeners.add(f);
	}
	
	public void removeFrameListener(FrameListener f)
	{
		window.frameListeners.remove(f);
	}
	
	public static Vector[] getDisplayModes()
	{
		return Window.getDisplayModes();
	}
	
	public Vector getDisplayMode()
	{
		return window.getDisplayMode();
	}
	
	public Quad addQuad(float width, float height)
	{
		return new Quad(geometryPool,width, height);
	}
	
	public Text addText(String font, int maxChars)
	{
		Font f = fontPool.get(font);
		if (f == null)
		{
			f = addFont(font);
		}
		return new Text(f,maxChars);
	}
	
	public Sprite addSprite(String spriteSheet)
	{
		SpriteSheet ss = spriteSheetPool.get(spriteSheet);
		if (ss == null)
		{
			ss = addSpriteSheet(spriteSheet);
		}
		return new Sprite(ss);
	}
	
	public ParticleSystem addParticleSystem(int particles, float life)
	{
		return new ParticleSystem(particles,life);
	}
	
	public Entity addTerrain(String heightMap, flexo.math.Coords size, flexo.math.Coords points, float maxHeight)
	{
		return new Terrain(geometryPool,heightMap,size,points,maxHeight);
	}
	
	public Entity addMesh(String fileName)
	{
		return new Mesh(geometryPool,fileName);
	}	
	
	/**
	 * Deletes all the cameras with the layer "layer".
	 * One camera must exist, so if you try to delete it, nothing will happen.
	 */
	void removeCamera(int layer)
	{
		if (cameraList.size() > 1)
		{
			for (Camera cam: cameraList)
			{
				if (cam.getLayer() == layer)
				{
					cameraList.remove(cam);
				}
			}
		}		
	}
	
	public Camera getCamera(int layer)
	{
		for (Camera cam: cameraList)
		{
			if (cam.getLayer() == layer)
			{
				return cam;
			}
		}
		return null;
	}
	
	protected int getNextCameraLayer()
	{
		int layer = -1;
		for (Camera c: cameraList)
		{
			if (c.getLayer() > layer)
			{
				layer = c.getLayer();
			}
		}
		return layer + 1;
	}
	
	public Camera addCamera3D(String scene)
	{
		return addCamera(scene,flexo.graphic.Camera.ProjectionType.PERSPECTIVE);
	}
	
	public Camera addCameraOrtho(String scene)
	{
		return addCamera(scene,flexo.graphic.Camera.ProjectionType.ORTHOGONAL);
	}
	
	public Camera addCamera(String scene, flexo.graphic.Camera.ProjectionType type)
	{
		Camera cam = new Camera(scene,type);
		cam.setLayer(getNextCameraLayer());
		cameraList.add(cam);
		sortCameraList = true;
		return cam;
	}
	
	public Font addFont(String file)
	{
		Font font = fontPool.get(file);
		if (font == null)
		{
			font = new Font(file);
			fontPool.add(file,font);
		}
		else
		{
			flexo.Kernel.log("flexo.graphic.GraphicSystem", "Font '"+file+"' already exists", LogMsgType.WARNING);
		}
		return font;
	}
	
	public boolean existsFont(String file)
	{
		Font f = fontPool.get(file);
		if (f == null)
		{
			return false;
		}
		return true;
	}
	
	public Font getFont(String file)
	{
		Font f = fontPool.get(file);
		if (f == null)
		{
			flexo.Kernel.log("flexo.graphic.GraphicSystem", "Font '"+file+"' doesn't exist", LogMsgType.ERROR);
			flexo.Kernel.error();
		}
		return f;
	}
	
	public SpriteSheet addSpriteSheet(String file)
	{
		SpriteSheet s = spriteSheetPool.get(file);
		if (s == null)
		{
			s = new SpriteSheet(file);
			spriteSheetPool.add(file, s);
		}
		else
		{
			flexo.Kernel.log("flexo.graphic.GraphicSystem", "Sprite sheet '"+file+"' already exists", LogMsgType.WARNING);
			flexo.Kernel.error();
		}
		return s;
	}
	
	public boolean existsSpriteSheet(String file)
	{
		SpriteSheet s = spriteSheetPool.get(file);
		if (s == null)
		{
			return false;
		}
		return true;
	}
	
	public SpriteSheet getSpriteSheet(String file)
	{
		SpriteSheet s = spriteSheetPool.get(file);
		if (s == null)
		{
			flexo.Kernel.log("flexo.graphic.GraphicSystem", "Sprite sheet '"+file+"' doesn't exist", LogMsgType.ERROR);
			flexo.Kernel.error();
		}
		return s;
	}
	
	public Material addMaterial(String name, String shader)
	{		
		Material mat = materialPool.get(name); 
		if (mat == null)
		{
			mat = new Material(name,shader);
			materialPool.add(name, mat);
		}
		else
		{
			flexo.Kernel.log("flexo.graphic.GraphicSystem", "Material '"+name+"' already exists", LogMsgType.WARNING);
		}
		return mat;
	}
	
	public Material addMaterial(String name)
	{
		return addMaterial(name,"default");
	}
	
	public boolean existsMaterial(String name)
	{
		Material m = materialPool.get(name);
		if (m == null)
		{
			return false;
		}
		return true;
	}
	
	public Material getMaterial(String name)
	{
		Material m = materialPool.get(name);
		if (m == null)
		{
			flexo.Kernel.log("flexo.graphic.GraphicSystem", "Material '"+name+"' doesn't exist", LogMsgType.ERROR);
		}
		return m;
	}
	
	public void logResources()
	{
		// Logging 'memory leaks'
		if (fontPool.getResourceCount() > 0)
			flexo.Kernel.log("flexo.graphic.GraphicSystem", fontPool.getResourceCount() + " fonts not released", LogMsgType.WARNING);
		if (geometryPool.getResourceCount() > 0)
			flexo.Kernel.log("flexo.graphic.GraphicSystem", geometryPool.getResourceCount() + " meshes not released", LogMsgType.WARNING);
		if (materialPool.getResourceCount() > 0)
			flexo.Kernel.log("flexo.graphic.GraphicSystem", materialPool.getResourceCount() + " materials not released", LogMsgType.WARNING);
		if (shaderPool.getResourceCount() > 0)
			flexo.Kernel.log("flexo.graphic.GraphicSystem", shaderPool.getResourceCount() + " shaders not released", LogMsgType.WARNING);
		if (spriteSheetPool.getResourceCount() > 0)
			flexo.Kernel.log("flexo.graphic.GraphicSystem", spriteSheetPool.getResourceCount() + " spritesheets not released", LogMsgType.WARNING);
		if (texturePool.getResourceCount() > 0)
			flexo.Kernel.log("flexo.graphic.GraphicSystem", texturePool.getResourceCount() + " textures not released", LogMsgType.WARNING);
	}

	@Override
	public void remove()
	{
		super.remove();			
		cleanResources();
		logResources();
		
		window.release();
	}
	
	@Override
	public void update()
	{
		if (sortCameraList)
		{
			Collections.sort(cameraList);
			sortCameraList = false;
		}
		
		try
		{
			window.render(cameraList);
		}
		catch (org.lwjgl.opengl.OpenGLException openGLe)
		{
			flexo.Kernel.log("flexo.graphic.GraphicSystem", "OpenGL error: " + openGLe.getMessage(), LogMsgType.ERROR);
			flexo.Kernel.error();
		}
	}
}
